using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Xml;

namespace Seaskyer.WebApp.Utility.BigUpload
{
	/// <summary>
	/// The core of SunriseUpload.
	/// </summary>
	public class HttpUploadModule : IHttpModule
	{
		private DateTime beginTime = DateTime.Now;

		private HttpUploadModule()
		{
		}

		/// <summary>
		/// Get value from preloaded entity body. Identified by name.
		/// </summary>
		/// <param name="preloadedEntityBody"></param>
		/// <param name="name"></param>
		/// <returns></returns>
		private string AnalysePreloadedEntityBody(byte[] preloadedEntityBody, string name)
		{
			string val = string.Empty;
			string preloadedContent = Utils.GetContext().Request.ContentEncoding.GetString(preloadedEntityBody);

			if (preloadedContent.Length > 0)
			{
				int startIndex = ((preloadedContent.IndexOf(("name=\"" + name + "\"")) + 11) + name.Length);
				int endIndex = preloadedContent.IndexOf("\r\n", startIndex);
				val = preloadedContent.Substring(startIndex, (endIndex - startIndex));
			}

			return val;
		}

		/// <summary>
		/// Event handler of request
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void Application_BeginRequest(object sender, EventArgs e)
		{
			HttpApplication application = (sender as HttpApplication);
			HttpWorkerRequest workerRequest = GetWorkerRequest();

			try
			{
				//Handle upload request only.
				if (!IsUploadRequest(application.Request))
				{
					return;
				}

				if (!workerRequest.HasEntityBody())
				{
					return;
				}

				//Define a local value to store the current position of total bytes.
				int currentPosition = 0;

				TimeSpan span = DateTime.Now.Subtract(this.beginTime);
				string contentType = application.Context.Request.ContentType.ToLower();

				byte[] boundaryData = Encoding.ASCII.GetBytes(("\r\n--" + contentType.Substring((contentType.IndexOf("boundary=") + 9))).ToCharArray());
				int FileLength = Convert.ToInt32(workerRequest.GetKnownRequestHeader(11));

				UploadStatus uploadStatus = new UploadStatus();
				application.Context.Items.Add("Sunrise_Web_Upload_FileList", new Hashtable());

				byte[] preloadedEntityBody = workerRequest.GetPreloadedEntityBody();
				currentPosition += preloadedEntityBody.Length;

				string uploadGuid = this.AnalysePreloadedEntityBody(preloadedEntityBody, "Sunrise_Web_Upload_UploadGUID");

				if (uploadGuid != string.Empty)
				{
					application.Context.Items.Add("Sunrise_Web_Upload_UploadGUID", uploadGuid);
				}

				bool isUploadFinished = true;
				if ((FileLength > this.GetUpLoadFileLength())
					&& ((0 > span.TotalHours) || (span.TotalHours > 3)))
				{
					isUploadFinished = false;
				}
				if ((0 > span.TotalHours) || (span.TotalHours > 3))
				{
					isUploadFinished = false;
				}

				string uploadFolder = this.AnalysePreloadedEntityBody(preloadedEntityBody, "Sunrise_Web_Upload_UploadFolder");
				if (uploadFolder.IndexOf(@":\") < 0)
				{
					uploadFolder = Path.GetTempPath();
				}

				ArrayList readBody = new ArrayList();
				RequestStream preloadedStream = new RequestStream(preloadedEntityBody, boundaryData,
				                                                  null, RequestStream.FileStatus.Close, RequestStream.ReadStatus.NoRead, uploadFolder, isUploadFinished, application.Context, string.Empty);

				readBody.AddRange(preloadedStream.ReadBody);

				//Set upload status.
				if (uploadGuid != string.Empty)
				{
					uploadStatus.FileLength = FileLength;
					uploadStatus.ReceivedLength = currentPosition;
					uploadStatus.FileName = preloadedStream.OriginalFileName;
					uploadStatus.FileCount = ((Hashtable) application.Context.Items["Sunrise_Web_Upload_FileList"]).Count;

					application.Application[("_UploadGUID_" + uploadGuid)] = uploadStatus;
				}

				//Is all data have been preload?
				if (!workerRequest.IsEntireEntityBodyIsPreloaded())
				{
					byte[] boudaryContent;
					ArrayList contentBody;

					//Define size of boundary.
					int boundarySize = 204800;
					byte[] boudaryBuffer = new byte[boundarySize];

					//Read each data block into read body array.
					while (((FileLength - currentPosition) >= boundarySize))
					{
						//If client is disconnected, clear all resources in use.
						if (!application.Context.Response.IsClientConnected)
						{
							this.ReleaseRes(application);
						}

						//Read bytes from request.
						boundarySize = workerRequest.ReadEntityBody(boudaryBuffer, boudaryBuffer.Length);
						currentPosition += boundarySize;

						contentBody = preloadedStream.ContentBody;

						if (contentBody.Count > 0)
						{
							boudaryContent = new byte[(contentBody.Count + boudaryBuffer.Length)];
							contentBody.CopyTo(boudaryContent, 0);
							boudaryBuffer.CopyTo(boudaryContent, contentBody.Count);
							preloadedStream = new RequestStream(boudaryContent, boundaryData,
							                                    preloadedStream.FileStream, preloadedStream.FStatus, preloadedStream.RStatus, uploadFolder, isUploadFinished, application.Context, preloadedStream.OriginalFileName);
						}
						else
						{
							preloadedStream = new RequestStream(boudaryBuffer, boundaryData,
							                                    preloadedStream.FileStream, preloadedStream.FStatus, preloadedStream.RStatus, uploadFolder, isUploadFinished, application.Context, preloadedStream.OriginalFileName);
						}

						//Append data block to read body array
						readBody.AddRange(preloadedStream.ReadBody);

						//Set upload status.
						if (uploadGuid != string.Empty)
						{
							uploadStatus.ReceivedLength = currentPosition;
							uploadStatus.FileName = preloadedStream.OriginalFileName;
							uploadStatus.FileCount = ((Hashtable) application.Context.Items["Sunrise_Web_Upload_FileList"]).Count;
							application.Application[("_UploadGUID_" + uploadGuid)] = uploadStatus;
						}
					}

					//The rest request data
					boudaryBuffer = new byte[FileLength - currentPosition];
					if (!application.Context.Response.IsClientConnected
						&& (preloadedStream.FStatus == RequestStream.FileStatus.Open))
					{
						this.ReleaseRes(application);
					}

					//Size of spare request data.
					boundarySize = workerRequest.ReadEntityBody(boudaryBuffer, boudaryBuffer.Length);
					contentBody = preloadedStream.ContentBody;
					if (contentBody.Count > 0)
					{
						boudaryContent = new byte[(contentBody.Count + boudaryBuffer.Length)];
						contentBody.CopyTo(boudaryContent, 0);
						boudaryBuffer.CopyTo(boudaryContent, contentBody.Count);
						preloadedStream = new RequestStream(boudaryContent, boundaryData, preloadedStream.FileStream, preloadedStream.FStatus, preloadedStream.RStatus, uploadFolder, isUploadFinished, application.Context, preloadedStream.OriginalFileName);
					}
					else
					{
						preloadedStream = new RequestStream(boudaryBuffer, boundaryData, preloadedStream.FileStream, preloadedStream.FStatus, preloadedStream.RStatus, uploadFolder, isUploadFinished, application.Context, preloadedStream.OriginalFileName);
					}

					//Append the rest data block to read body array.
					readBody.AddRange(preloadedStream.ReadBody);
					if (uploadGuid != string.Empty)
					{
						uploadStatus.ReceivedLength = (currentPosition + boudaryBuffer.Length);
						uploadStatus.FileName = preloadedStream.OriginalFileName;
						uploadStatus.FileCount = ((Hashtable) application.Context.Items["Sunrise_Web_Upload_FileList"]).Count;

						if (isUploadFinished)
						{
							uploadStatus.State = UploadState.Uploaded;
						}
						else
						{
							application.Application.Remove(("_UploadGUID_" + uploadGuid));
						}
					}
				}

				//Copy all data to byte buffer.
				byte[] readedBodyBuffer = new byte[readBody.Count];
				readBody.CopyTo(readedBodyBuffer);

				this.InjectTextParts(workerRequest, readedBodyBuffer);

				ClearApplication(application);
			}
			catch (Exception exception)
			{
				//If exception has been throw, clear all resources in use.
				this.ReleaseRes(application);
				throw exception;
			}
		}

		private HttpWorkerRequest GetWorkerRequest()
		{
			IServiceProvider provider = HttpContext.Current;
			return ((HttpWorkerRequest) provider.GetService(typeof (HttpWorkerRequest)));
		}

		private bool IsUploadRequest(HttpRequest request)
		{
			return request.ContentType.ToLower().StartsWith("multipart/form-data");
		}

		/// <summary>
		/// Even handler of end request.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void Application_EndRequest(object sender, EventArgs e)
		{
			HttpApplication application = (sender as HttpApplication);
			application.Context.Items.Clear();
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void Application_Error(object sender, EventArgs e)
		{
			HttpApplication application = (sender as HttpApplication);
			this.ReleaseRes(application);
		}

		/// <summary>
		/// Clear all context items, delete temporary file.
		/// </summary>
		/// <param name="application"></param>
		private void ReleaseRes(HttpApplication application)
		{
			ClearApplication(application);

			if (application.Context.Items["Sunrise_Web_Upload_FileList"] != null)
			{
				Hashtable fileList = ((Hashtable) application.Context.Items["Sunrise_Web_Upload_FileList"]);

				foreach (object obj in fileList.Values)
				{
					if (!File.Exists(obj.ToString()))
					{
						continue;
					}

					File.Delete(obj.ToString());
				}
			}

			application.Context.Items.Clear();
		}

		private void ClearApplication(HttpApplication application)
		{
			if ((application.Context.Items["Sunrise_Web_Upload_FileStatus"] != null)
				&& (((byte) ((RequestStream.FileStatus) application.Context.Items["Sunrise_Web_Upload_FileStatus"])) == 0))
			{
				FileStream fileStream = ((FileStream) application.Context.Items["Sunrise_Web_Upload_FileStream"]);
				fileStream.Close();
			}
	
			if (application.Context.Items["Sunrise_Web_Upload_UploadGUID"] != null)
			{
				string uploadGuid = ((string) application.Context.Items["Sunrise_Web_Upload_UploadGUID"]);
				application.Application.Remove(("_UploadGUID_" + uploadGuid));
			}
		}

		/// <summary>
		/// 
		/// </summary>
		public void Dispose()
		{
			
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="application"></param>
		public void Init(HttpApplication application)
		{
			application.BeginRequest += new EventHandler(this.Application_BeginRequest);
			application.EndRequest += new EventHandler(this.Application_EndRequest);
			application.Error += new EventHandler(this.Application_Error);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="request"></param>
		/// <param name="textParts"></param>
		/// <returns></returns>
		private byte[] InjectTextParts(HttpWorkerRequest request, byte[] textParts)
		{
			Type type;
			BindingFlags flags = (BindingFlags.NonPublic | BindingFlags.Instance);

			//Is there application host IIS6.0?
			if (Utils.GetContext().Request.ServerVariables["SERVER_SOFTWARE"].Equals("Microsoft-IIS/6.0"))
			{
				type = request.GetType().BaseType.BaseType;
			}
			else
			{
				type = request.GetType().BaseType;
			}

			int dataLength = textParts.Length;

			//Set values of working request
			type.GetField("_contentAvailLength", flags).SetValue(request, dataLength);
			type.GetField("_contentTotalLength", flags).SetValue(request, dataLength);
			type.GetField("_preloadedContent", flags).SetValue(request, textParts);
			type.GetField("_preloadedContentRead", flags).SetValue(request, true);

			return textParts;
		}

		/// <summary>
		/// Get value of "maxRequestLength" setting.
		/// </summary>
		/// <returns>
		/// If defined maxRequestLength setting in web.config
		/// and it value small then the one which defined in mechine.config, get it.
		/// Otherwise get it from mechine.config.
		/// </returns>
		private double GetUpLoadFileLength()
		{
			string assemblyLocation = typeof (string).Assembly.Location;

			assemblyLocation = Path.GetDirectoryName(assemblyLocation);
			assemblyLocation = Path.Combine(assemblyLocation, @"CONFIG\machine.config");

			XmlDocument xmlDocument = new XmlDocument();

			//Load mechine.config
			xmlDocument.Load(assemblyLocation);

			double maxRequestLength = Convert.ToDouble(xmlDocument.SelectSingleNode("configuration/system.web/httpRuntime/@maxRequestLength").Value);

			//Load web.config.
			xmlDocument.Load(Path.Combine(Utils.GetContext().Request.PhysicalApplicationPath, "web.config"));
			XmlNode node = xmlDocument.SelectSingleNode("configuration/system.web/httpRuntime/@maxRequestLength");

			if (node != null)
			{
				double length = Convert.ToDouble(node.Value);

				if (length < maxRequestLength)
				{
					maxRequestLength = length;
				}
			}

			//Release xml document object.
			xmlDocument = null;

			return (maxRequestLength*1024);
		}

	}

}